package edu.hcmus.sow.service;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.acls.domain.ObjectIdentityImpl;
import org.springframework.security.acls.model.AccessControlEntry;
import org.springframework.security.acls.model.MutableAcl;
import org.springframework.security.acls.model.MutableAclService;
import org.springframework.security.acls.model.NotFoundException;
import org.springframework.security.acls.model.ObjectIdentity;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.acls.model.Sid;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import edu.hcmus.sow.domain.security.CustomUser;

@Service
@Transactional
public class SecurityService {
   @Autowired
   private MutableAclService aclService;

   @Autowired
   private PermissionEvaluator permissionEvaluator;

   @SuppressWarnings("rawtypes")
   public void addPermission(Class clazz, Serializable id, Sid recipient, Permission permission) {
      MutableAcl acl;
      ObjectIdentity oid = new ObjectIdentityImpl(clazz, id);

      try {
         acl = (MutableAcl) aclService.readAclById(oid);
      } catch (NotFoundException nfe) {
         acl = aclService.createAcl(oid);
      }

      acl.insertAce(acl.getEntries().size(), permission, recipient, true);
      aclService.updateAcl(acl);
   }

   @SuppressWarnings("rawtypes")
   public void removePermission(Class clazz, Serializable id, Sid recipient, Permission permission) {
      ObjectIdentity oid = new ObjectIdentityImpl(clazz, id);
      MutableAcl acl = (MutableAcl) aclService.readAclById(oid);

      // Remove all permissions associated with this particular recipient
      List<AccessControlEntry> entries = acl.getEntries();

      for (int i = 0; i < entries.size(); i++) {
         if (entries.get(i).getSid().equals(recipient) && entries.get(i).getPermission().equals(permission)) {
            acl.deleteAce(i);
         }
      }

      aclService.updateAcl(acl);
   }

   @SuppressWarnings("rawtypes")
   public void grantPermissions(Class clazz, Serializable id, Sid recipient, Set<Permission> permissions) {
      MutableAcl acl;
      ObjectIdentity oid = new ObjectIdentityImpl(clazz, id);

      try {
         acl = (MutableAcl) aclService.readAclById(oid);
      } catch (NotFoundException nfe) {
         acl = aclService.createAcl(oid);
      }

      List<AccessControlEntry> entries = acl.getEntries();
      for (int i = entries.size() - 1; i >= 0; i--) {
         if (entries.get(i).getSid().equals(recipient)) {
            acl.deleteAce(i);
         }
      }

      for (Permission permission : permissions) {
         acl.insertAce(acl.getEntries().size(), permission, recipient, true);
      }

      aclService.updateAcl(acl);
   }

   @SuppressWarnings("rawtypes")
   public Set<Permission> getPermissions(Class clazz, Serializable id, Sid recipient) {
      Set<Permission> permissions = new HashSet<Permission>();

      try {
         MutableAcl acl = (MutableAcl) aclService.readAclById(new ObjectIdentityImpl(clazz, id));
         for (AccessControlEntry entry : acl.getEntries()) {
            if (entry.getSid().equals(recipient)) {
               permissions.add(entry.getPermission());
            }
         }
      } catch (NotFoundException nfe) {
      }

      return permissions;
   }

   public boolean hasPermission(Object domainObject, Permission... permission) {
      return permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), domainObject,
            permission);
   }

   @SuppressWarnings("rawtypes")
   public boolean hasPermission(Class clazz, Serializable id, Permission... permission) {
      return permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), id,
            clazz.getName(), permission);
   }

   public boolean hasRole(String role) {
      CustomUser user = (CustomUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
      Set<GrantedAuthority> dbAuthsSet = (Set<GrantedAuthority>) user.getAuthorities();
      List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);

      for (GrantedAuthority auth : dbAuths) {
         if (((SimpleGrantedAuthority) auth).getAuthority().equals(role))
            return true;
      }
      return false;
   }

   public boolean hasRole(String... roles) {
      for (String role : roles) {
         if (hasRole(role))
            return true;
      }
      return false;
   }

   public boolean hasAccessRight(String accessRight, String... rights) {
      for (String ac : rights) {
         if (ac.equals(accessRight))
            return true;
      }
      return false;
   }
}
